package editor.logic;

import moltencore.game.CannonData;
import moltencore.game.loader.Level;
import moltencore.game.loader.LevelSprite;
import moltencore.game.loader.LevelParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;

import spark.math.Vector2;
import spark.exception.LoadingException;

import java.util.*;
import java.io.OutputStream;

import moltencore.game.collision.HorizontalLine;
import moltencore.game.collision.VerticalLine;
import moltencore.game.bubble.Bubble;

public class LevelSaver {
	protected String dtd = "level.dtd";
	protected Document document;

	public void save(Level level, OutputStream destination) throws LoadingException {
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			Element root = document.createElement(LevelParser.ELEMENT_LEVEL);
			root.setAttribute(LevelParser.ATTRIBUTE_NAME, level.getName());
			document.appendChild(root);
			for(CannonData cannonData : level.getCannonData()) {
				Element cannonDataElement = document.createElement(LevelParser.ELEMENT_CANNON_DATA);
				cannonDataElement.setAttribute(LevelParser.ATTRIBUTE_CANNON_INDEX, Integer.toString(cannonData.getIndex()));
				appendVector2(cannonDataElement, LevelParser.ELEMENT_CANNON_POSITION, cannonData.getPosition());
				appendVector2(cannonDataElement, LevelParser.ELEMENT_CANNON_NEXT_POSITION, cannonData.getNextPosition());
				appendVector2(cannonDataElement, LevelParser.ELEMENT_CANNON_TARGET, cannonData.getTarget());
			}
			appendVector2(root, LevelParser.ELEMENT_RESULT_POSITION, level.getResultPosition());
			appendVector2(root, LevelParser.ELEMENT_SCORE_POSITION, level.getScorePosition());
			appendVector2(root, LevelParser.ELEMENT_NICK_POSITION, level.getNickPosition());
			appendVector2(root, LevelParser.ELEMENT_CLOCK_POSITION, level.getClockPosition());
			appendWalls(root, level.getHorizontalLines(), level.getVerticalLines());
			appendCriticalAreas(root, level.getCriticalAreas());
			appendKillAreas(root, level.getKillAreas());
			appendRestrictedAreas(root, level.getRestrictedAreas());
			if(level.getAttachedBubbles() != null) {
				appendBubbles(root, level.getAttachedBubbles());
			}
			if(level.getPushBubbles() != null) {
				appendPushBubbles(root, level.getPushBubbles());
			}
			if(level.getFillBubbles() != null) {
				appendFillBubbles(root, level.getFillBubbles());
			}
			appendGeometry(root, level.getLevelSprites());

			Transformer transformer;
			transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
			transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dtd);
			transformer.transform(new DOMSource(document), new StreamResult(destination));
		}
		catch(ParserConfigurationException e) {
			throw new LoadingException(e);
		}
		catch(TransformerConfigurationException e) {
			throw new LoadingException(e);
		}
		catch(TransformerException e) {
			throw new LoadingException(e);
		}
	}

	protected void appendVector2(Element appendee, String elementName, Vector2 vector2) {
		if(vector2 == null) {
			return;
		}
		Element vector2Element = document.createElement(elementName);
		vector2Element.setAttribute(LevelParser.ATTRIBUTE_X, Float.toString(vector2.x));
		vector2Element.setAttribute(LevelParser.ATTRIBUTE_Y, Float.toString(vector2.y));
		appendee.appendChild(vector2Element);
	}

	protected void appendWalls(Element appendee, Collection<HorizontalLine> horizontals, Collection<VerticalLine> verticals) {
		for(HorizontalLine wall : horizontals) {
			Element wallElement = document.createElement(LevelParser.ELEMENT_WALL);
			wallElement.setAttribute(LevelParser.ATTRIBUTE_TYPE, LevelParser.LineType.Horizontal.toString());
			wallElement.setAttribute(LevelParser.ATTRIBUTE_STICKY, Boolean.toString(wall.isSticky()));
			wallElement.setAttribute(LevelParser.ATTRIBUTE_Y_START, Float.toString(wall.getYPosition()));
			appendee.appendChild(wallElement);
		}
		for(VerticalLine wall : verticals) {
			Element wallElement = document.createElement(LevelParser.ELEMENT_WALL);
			wallElement.setAttribute(LevelParser.ATTRIBUTE_TYPE, LevelParser.LineType.Vertical.toString());
			wallElement.setAttribute(LevelParser.ATTRIBUTE_STICKY, Boolean.toString(wall.isSticky()));
			wallElement.setAttribute(LevelParser.ATTRIBUTE_X_START, Float.toString(wall.getXPosition()));
			appendee.appendChild(wallElement);
		}
	}

	protected void appendCriticalAreas(Element appendee, Collection<HorizontalLine> areas) {
		for(HorizontalLine area : areas) {
			Element element = document.createElement(LevelParser.ELEMENT_CRITICAL_AREA);
			element.setAttribute(LevelParser.ATTRIBUTE_TYPE, LevelParser.LineType.Horizontal.toString());
			element.setAttribute(LevelParser.ATTRIBUTE_Y_START, Float.toString(area.getYPosition()));
			appendee.appendChild(element);
		}
	}

	protected void appendKillAreas(Element appendee, Collection<HorizontalLine> areas) {
		for(HorizontalLine area : areas) {
			Element element = document.createElement(LevelParser.ELEMENT_KILL_AREA);
			element.setAttribute(LevelParser.ATTRIBUTE_TYPE, LevelParser.LineType.Horizontal.toString());
			element.setAttribute(LevelParser.ATTRIBUTE_Y_START, Float.toString(area.getYPosition()));
			appendee.appendChild(element);
		}
	}

	protected void appendRestrictedAreas(Element appendee, Collection<HorizontalLine> areas) {
		for(HorizontalLine area : areas) {
			Element element = document.createElement(LevelParser.ELEMENT_RESTRICTED_AREA);
			element.setAttribute(LevelParser.ATTRIBUTE_TYPE, LevelParser.LineType.Horizontal.toString());
			element.setAttribute(LevelParser.ATTRIBUTE_Y_START, Float.toString(area.getYPosition()));
			appendee.appendChild(element);
		}
	}

	protected void appendBubbles(Element appendee, LinkedList<Bubble> bubbles) {
		for(Bubble bubble : bubbles) {
			Element bubbleElement = document.createElement(LevelParser.ELEMENT_BUBBLE);
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_TYPE, bubble.getType().toString());
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_X, Integer.toString(bubble.getGridPosition().x));
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_Y, Integer.toString(bubble.getGridPosition().y));
			appendee.appendChild(bubbleElement);
		}
	}

	protected void appendPushBubbles(Element appendee, LinkedList<Bubble> pushBubbles) {
		for(Bubble bubble : pushBubbles) {
			Element bubbleElement = document.createElement(LevelParser.ELEMENT_PUSH_BUBBLE);
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_X, Integer.toString(bubble.getGridPosition().x));
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_Y, Integer.toString(bubble.getGridPosition().y));
			appendee.appendChild(bubbleElement);
		}
	}

	protected void appendFillBubbles(Element appendee, LinkedList<Bubble> fillBubbles) {
		for(Bubble bubble : fillBubbles) {
			Element bubbleElement = document.createElement(LevelParser.ELEMENT_FILL_BUBBLE);
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_X, Integer.toString(bubble.getGridPosition().x));
			bubbleElement.setAttribute(LevelParser.ATTRIBUTE_GRID_Y, Integer.toString(bubble.getGridPosition().y));
			appendee.appendChild(bubbleElement);
		}
	}

	protected void appendGeometry(Element appendee, LinkedList<LevelSprite> levelSprites) {
		for(LevelSprite sprite : levelSprites) {
			Element spriteElement = document.createElement(LevelParser.ELEMENT_LEVEL_SPRITE);
			String[] mapNamePieces = sprite.getSpriteMap().split("[/\\\\\\\\]");
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_SPRITE_MAP, mapNamePieces[mapNamePieces.length - 1]);
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_THEME, sprite.getTheme());
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_ID, sprite.getId());
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_POS_X, Float.toString(sprite.getRenderable().getPosition().x));
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_POS_Y, Float.toString(sprite.getRenderable().getPosition().y));
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_POS_Z, Float.toString(sprite.getRenderable().getPosition().z));
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_SCALE_X, Float.toString(sprite.getRenderable().getDimensions().x));
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_SCALE_Y, Float.toString(sprite.getRenderable().getDimensions().y));
			spriteElement.setAttribute(LevelParser.ATTRIBUTE_ROT, Float.toString(0));
			appendee.appendChild(spriteElement);
		}
	}
}
